namespace PugLib.Benchmarks
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.Globalization;
	using System.Linq;
	using System.Threading;
	using System.Threading.Tasks;

	using PugLib.Extensions;
	using PugLib.Threading;

	public static class Threads
	{
		private static int _dividedItemCount;
		private static int _doneThreads;

		#region Threading

		private static readonly Func<int, int> _square = x => (x * x);


		public static TestResult PugParallelMap2()
		{
			Console.WriteLine("ParallelMap");
			TestResult trd = new TestResult("ParallelMap");

			List<int> subTotals = Factory.GenerateRepeatedList(1, Statics.MapTestCount);

			trd.Start();
			subTotals.ParallelMap(_square);
			trd.Stop();

			return trd;
		}


		public static TestResult ListIntLinqSimplest()
		{
			Console.WriteLine("ListIntLinqSimplest");
			TestResult trd = new TestResult("ListIntLinqSimplest");

			int marx = (int)Statics.Max;

			List<int> li = Factory.GenerateRepeatedList(1, Statics.ItemCount);
			List<long> subTotals = Factory.GenerateRepeatedList((long)0, marx);

			trd.Start();
			Parallel.For(0, marx, j => { subTotals[j] = li.Sum(); });
			long total = subTotals.Sum();

			trd.Stop();

			Debug.Assert(total == Statics.AssertValue);
			return trd;
		}


		public static TestResult ListIntLinqFastLambda()
		{
			Console.WriteLine("ListIntLinqFastLambda");
			TestResult trd = new TestResult("ListIntLinqFastLambda");

			int marx = (int)Statics.Max;
			List<int> li = Factory.GenerateRepeatedList(1, Statics.ItemCount);
			List<long> subTotals = Factory.GenerateRepeatedList((long)0, marx);
			Int64 total = 0;

			trd.Start();
			Parallel.For(0, marx, j =>
			                      {
			                      	long subTotal = 0;
			                      	li.ForEach(q => subTotal += q);
			                      	subTotals[j] = subTotal;
			                      });
			subTotals.ForEach(part => total += part);
			trd.Stop();

			Debug.Assert(total == Statics.AssertValue);
			return trd;
		}

		public static TestResult ListIntLinqOldStyle()
		{
			Console.WriteLine("ListIntLinqOldStyle");
			TestResult trd = new TestResult("ListIntLinqOldStyle");

			int marx = (int)Statics.Max;
			List<int> li = Factory.GenerateRepeatedList(1, Statics.ItemCount);
			List<long> subTotals = Factory.GenerateRepeatedList((long)0, marx);
			Int64 total = 0;

			trd.Start();
			Parallel.For(0, marx, j =>
			                      {
			                      	long subTotal = 0;
			                      	for (int q = 0; q < Statics.ItemCount; q++)
			                      	{
			                      		subTotal += li[q];
			                      	}
			                      	subTotals[j] = subTotal;
			                      });

			subTotals.ForEach(part => total += part);

			trd.Stop();

			Debug.Assert(total == Statics.AssertValue);
			return trd;
		}


		public static TestResult ListIntGenericsThreads(int threadCount)
		{
			string testName = string.Format(CultureInfo.InvariantCulture, "ListIntGenericsThreads {0} threads",
			                                threadCount.ToString(CultureInfo.InvariantCulture).PadLeft(3));
			Console.WriteLine(testName);
			_doneThreads = 0; // reset end condition    
			Statics.SumItemCount = 0;
			TestResult trd = new TestResult(testName);

			// set up the distributed work load
			int totalCount = Statics.ItemCount;
			// partition the workload evenly between threads          
			List<int> dividedItemCounts = PartitionWorkload(threadCount, totalCount);

			List<Thread> threads = new List<Thread>(threadCount);
			Dictionary<int, List<int>> lis = new Dictionary<int, List<int>>(threadCount);

			// set up threads
			for (int j = 0; j < threadCount; j++)
			{
				// add list
				lis.Add(j, new List<int>(dividedItemCounts[j]));

				// fill each list
				for (int i = 0; i < dividedItemCounts[j]; i++)
				{
					lis[j].Add(1);
				}

				ListIntThreadData td = new ListIntThreadData(lis[j]);
				ThreadWithState tws = new ThreadWithState(td, ListIntGenericsThreadsCallBack);

				Thread t = new Thread(tws.ThreadProc);
				threads.Add(t);
			}

			// spawn work

			trd.Start();
			for (int j = 0; j < threads.Count; j++)
			{
				threads[j].Start();
			}

			// block until all threads finish
			while (_doneThreads != threadCount)
			{
			}
			trd.Stop();

			// fill return value

			// cleanup
			lis.Clear();

			// summary
			Debug.Assert(Statics.SumItemCount == Statics.AssertValue);

			return trd;
		}

		public static List<int> PartitionWorkload(int threadCount, int totalCount)
		{
			List<int> dividedItemCounts = GetDividedItemCountList(threadCount);
			while (totalCount > 0)
			{
				if (totalCount < threadCount)
				{
					_dividedItemCount = 1;
				}
				else
				{
					_dividedItemCount = totalCount / threadCount;
				}
				for (int i = 0; i < threadCount && totalCount > 0; i++)
				{
					dividedItemCounts[i] += _dividedItemCount;
					totalCount -= _dividedItemCount;
				}
			}
			return dividedItemCounts;
		}

		private static void ListIntGenericsThreadsCallBack(AbstractThreadData td)
		{
			Statics.SumItemCount += (long)td.Result;
			_doneThreads++;
		}

		#endregion

		#region BGWorker

		public static TestResult ListIntGenericsBackgroundWorker(int threadCount)
		{
			string testName = string.Format(CultureInfo.InvariantCulture,
			                                "ListIntGenericsBGW {0} threads", threadCount.ToString(CultureInfo.InvariantCulture).PadLeft(3));
			Console.WriteLine(testName);
			_doneThreads = 0; // reset end condition    
			Statics.SumItemCount = 0;
			TestResult trd = new TestResult(testName);

			// set up the distributed work load
			int totalCount = Statics.ItemCount;
			// partition the workload evenly between threads
			List<int> dividedItemCounts = PartitionWorkload(threadCount, totalCount);

			List<BackgroundWorker> workers = new List<BackgroundWorker>(threadCount);
			Dictionary<int, List<int>> lis = new Dictionary<int, List<int>>(threadCount);

			// set up bgworkers
			for (int j = 0; j < threadCount; j++)
			{
				// hook up bgworkers
				BackgroundWorker bgw = new BackgroundWorker();
				bgw.DoWork += (ListIntGenericsBGW_DoWork);
				bgw.RunWorkerCompleted += (ListIntGenericsBGW_RunWorkerCompleted);
				workers.Add(bgw);

				// add list
				lis.Add(j, new List<int>(dividedItemCounts[j]));

				// fill each list
				for (int i = 0; i < dividedItemCounts[j]; i++)
				{
					lis[j].Add(1);
				}
			}

			// spawn work

			trd.Start();
			for (int j = 0; j < workers.Count; j++)
			{
				workers[j].RunWorkerAsync(new ListIntThreadData(Guid.NewGuid(), lis[j]));
			}

			// block until all threads finish
			while (_doneThreads != threadCount)
			{
			}
			trd.Stop();

			// fill return value

			// cleanup
			workers.ForEach(bgw =>
			                {
			                	bgw.DoWork -= (ListIntGenericsBGW_DoWork);
			                	bgw.RunWorkerCompleted -= (ListIntGenericsBGW_RunWorkerCompleted);
			                	bgw.Dispose();
			                });
			lis.Clear();

			Debug.Assert(Statics.SumItemCount == Statics.AssertValue);
			return trd;
		}

		private static List<int> GetDividedItemCountList(int threadCount)
		{
			return Factory.GenerateRepeatedList(0, threadCount);
		}

		private static void ListIntGenericsBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			AbstractThreadData data = e.Result as AbstractThreadData;
			if (data != null)
			{
				Statics.SumItemCount += (long)(data.Result);
			}
			_doneThreads++;
		}

		private static void ListIntGenericsBGW_DoWork(object sender, DoWorkEventArgs e)
		{
			ListIntThreadData td = e.Argument as ListIntThreadData;
			Int64 threadtotal = 0;

			if (td == null)
			{
				return;
			}

			for (int j = 0; j < Statics.Max; j++)
			{
				td.Items.ForEach(foo => threadtotal += foo);
			}
			e.Result = new ListIntThreadData(td.Id, threadtotal);
		}

		#endregion BGWorker
	}
}